package com.lq.filestudy.utils;

import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.apache.commons.io.input.NullInputStream;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.util.List;
import java.util.concurrent.*;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author youlingdada youlingdada@163.com
 * @version 1.2
 * @createDate 2022/1/30 11:15
 */
public class FileZipCompressUtils {

    /**
     * cpu核心数
     */
    private static final int CPU_CORE = Runtime.getRuntime().availableProcessors();


    /**
     * 压缩文件夹
     *
     * @param zipOutName zip输出路径
     * @param paths      将要压缩的路径列表
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void compressFiles(String zipOutName, List<String> paths) throws IOException, ExecutionException, InterruptedException {
//        创建一个线程池对象
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
//        压缩等级默认为速度优先
        compressFiles(zipOutName, executor, Deflater.BEST_SPEED, paths);
    }

    /**
     * 压缩文件夹
     *
     * @param zipOutName zip输出路径
     * @param paths      将要压缩的路径列表
     * @param parentPath 父文件目录
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void compressFiles(String zipOutName, List<String> paths, String parentPath) throws IOException, ExecutionException, InterruptedException {
//        创建一个线程池对象
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
//        压缩等级默认为速度优先
        compressFiles(zipOutName, executor, Deflater.BEST_SPEED, paths);
    }


    /**
     * 压缩文件夹
     *
     * @param paths 将要压缩的路径列表
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static String compressFiles(List<String> paths) throws IOException, ExecutionException, InterruptedException {
//        创建一个线程池对象
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
//        压缩等级默认为速度优先
        return compressFiles(executor, Deflater.BEST_SPEED, paths);
    }

    /**
     * 自定义线程池
     *
     * @param zipOutName
     * @param executorService 线程池实现对象
     * @param paths
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void compressFiles(String zipOutName, ExecutorService executorService, int level, List<String> paths) throws IOException, ExecutionException, InterruptedException {
//      创建用于多线程压缩文件的对象
        ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executorService);
//        输出文件流
        OutputStream outputStream = new FileOutputStream(zipOutName);
//        输出Zip文件流
        ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream);
//        设置压缩等级
        zipArchiveOutputStream.setLevel(level);
//        设置压缩的字符编码
        zipArchiveOutputStream.setEncoding("UTF-8");
//        循环压缩各个路径的文件
        for (String path : paths) {
            File temp = new File(path);
            compress(parallelScatterZipCreator, temp, temp.getName());
        }
//        将数据写入zip输出流
        parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
//        相关流的关闭
        zipArchiveOutputStream.close();
        outputStream.close();
    }

    /**
     * 自定义线程池
     *
     * @param executorService 线程池实现对象
     * @param paths
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static String compressFiles(ExecutorService executorService, int level, List<String> paths) throws IOException, ExecutionException, InterruptedException {
//      创建临时文件
        final File tempFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".zip");
        //      创建用于多线程压缩文件的对象
        ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executorService);
//        输出文件流
        OutputStream outputStream = new FileOutputStream(tempFile);
//        输出Zip文件流
        ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream);
//        设置压缩等级
        zipArchiveOutputStream.setLevel(level);
//        设置压缩的字符编码
        zipArchiveOutputStream.setEncoding("UTF-8");
//        循环压缩各个路径的文件
        for (String path : paths) {
            File temp = new File(path);
            compress(parallelScatterZipCreator, temp, temp.getName());
        }
//        将数据写入zip输出流
        parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
//        相关流的关闭
        zipArchiveOutputStream.close();
        outputStream.close();
//       返回压缩后文件了路径
        return tempFile.getAbsolutePath();
    }


    /**
     * 自定义线程创建工厂
     *
     * @param zipOutName
     * @param factory    线程创建工厂
     * @param level      压缩等级
     * @param paths      需要压缩的文件路径了列表
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void compressFiles(String zipOutName, ThreadFactory factory, int level, List<String> paths) throws IOException, ExecutionException, InterruptedException {
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), factory, new ThreadPoolExecutor.CallerRunsPolicy());
        compressFiles(zipOutName, executor, level, paths);
    }


    /**
     * 自定义线程创建工厂
     *
     * @param factory 线程创建工厂
     * @param level   压缩等级
     * @param paths   需要压缩的文件路径了列表
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static String compressFiles(ThreadFactory factory, int level, List<String> paths) throws IOException, ExecutionException, InterruptedException {
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), factory, new ThreadPoolExecutor.CallerRunsPolicy());
        return compressFiles(executor, level, paths);
    }

    /**
     * 压缩文件夹，跳过多个级文件夹
     *
     * @param compressPaths 需要压缩的文件路径
     * @param dirPath       压缩包根目录
     * @return 压缩后文件路径
     */
    public static String compressToDirs(List<String> compressPaths, String dirPath) throws IOException, ExecutionException, InterruptedException {
//        创建临时文件
        final File tempFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".zip");
//        创建多线程压缩线程池
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
//        创建用于多线程压缩文件的对象
        ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);
//        输出文件流
        OutputStream outputStream = Files.newOutputStream(tempFile.toPath());
//        输出Zip文件流
        ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream);
//        设置压缩等级
        zipArchiveOutputStream.setLevel(Deflater.BEST_SPEED);
//        设置压缩的字符编码
        zipArchiveOutputStream.setEncoding("UTF-8");
//        压缩
//        tag 标记，避免文件夹重名
        if (compressPaths.size() > 1) {
            for (String compressPath : compressPaths) {
                compressPath = compressPath.replace("\\", "/");
                String[] split = compressPath.split("/");
                String fileName = split[split.length - 1];
                compress(parallelScatterZipCreator, new File(compressPath), dirPath + fileName);
            }
        } else {
            compress(parallelScatterZipCreator, new File(compressPaths.get(0)), dirPath);
        }

//        将数据写入zip输出流
        parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
//        相关流的关闭
        zipArchiveOutputStream.close();
        outputStream.close();
//       返回压缩后文件了路径
        return tempFile.getAbsolutePath();
    }


    /**
     * 遍历压缩
     *
     * @param parallelScatterZipCreator 线程池压缩对象
     * @param inputFile                 将要压缩的文件路径,绝对路径
     * @param relativePath              相对与压缩包内的路径
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    protected static void compress(ParallelScatterZipCreator parallelScatterZipCreator, File inputFile, String relativePath) throws IOException, ExecutionException, InterruptedException {
//        文件流为空，返回
        if (inputFile == null) {
            return;
        }
//        文件为文件夹，递归遍历文件
        InputStreamSupplier inputStreamSupplier = null;
        if (inputFile.isDirectory()) {
//            获取文件内的所有文件
            File[] files = inputFile.listFiles();
            if (files == null) {
                return;
            }
//            遍历处理文件
            for (File file : files) {
                if (file.isDirectory()) {
                    compress(parallelScatterZipCreator, new File(inputFile.getAbsolutePath() + "/" + file.getName()), relativePath + "/" + file.getName());
                } else {
//                    转化为InputStreamSupplier对象
                    inputStreamSupplier = () -> {
                        try {
                            return new FileInputStream(file);
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                            return new NullInputStream(0);
                        }
                    };
//                    添加ZipArchiveEntity对象，这里的构造函数的值，name属性，是相对于zip文件内的路径
                    ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(relativePath + "/" + file.getName());
//                    设置压缩算法
                    zipArchiveEntry.setMethod(ZipArchiveEntry.DEFLATED);
//                    设置未压缩文件的大小
                    zipArchiveEntry.setSize(file.length());
//                    添加添加ZipArchiveEntity对象到多线程压缩中
                    parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
                }
            }
        } else {
//            当是文件时，直接处理
            inputStreamSupplier = () -> {
                try {
                    return new FileInputStream(inputFile);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    return new NullInputStream(0);
                }
            };
            ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(relativePath + "/" + inputFile.getName());
            zipArchiveEntry.setMethod(ZipArchiveEntry.DEFLATED);
            zipArchiveEntry.setSize(inputFile.length());
            parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
        }
    }


    // 合并压缩文件
    public static String compressMerge(List<String> paths, String basePath) throws IOException, ExecutionException, InterruptedException {

        InputStreamSupplier inputStreamSupplier = null;

        //        创建一个线程池对象
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        //      创建用于多线程压缩文件的对象
        ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);

        //      创建临时文件
        final File tempFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".zip");

        //        输出文件流
        OutputStream outputStream = Files.newOutputStream(tempFile.toPath());
//        输出Zip文件流
        ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream);
//        设置压缩等级
        zipArchiveOutputStream.setLevel(Deflater.BEST_SPEED);
//        设置压缩的字符编码
        zipArchiveOutputStream.setEncoding("UTF-8");

        for (String path : paths) {
            File file = new File(basePath + path);
            inputStreamSupplier = () -> {
                try {
                    return new FileInputStream(file);
                } catch (FileNotFoundException e) {
                    return new NullInputStream(0);
                }
            };
            ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(path);
            zipArchiveEntry.setMethod(ZipArchiveEntry.DEFLATED);
            zipArchiveEntry.setSize(file.length());
            parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
        }

        //        将数据写入zip输出流
        parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
//        相关流的关闭
        zipArchiveOutputStream.close();
        outputStream.close();
//       返回压缩后文件了路径
        return tempFile.getAbsolutePath();
    }

    // 合并压缩文件
    public static String compressMergeOnlyFile(List<String> paths, String basePath) throws IOException, ExecutionException, InterruptedException {

        InputStreamSupplier inputStreamSupplier = null;

        //        创建一个线程池对象
        ExecutorService executor = new ThreadPoolExecutor(CPU_CORE, CPU_CORE * 2 + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        //      创建用于多线程压缩文件的对象
        ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);

        //      创建临时文件
        final File tempFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".zip");

        //        输出文件流
        OutputStream outputStream = new FileOutputStream(tempFile);
//        输出Zip文件流
        ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream);
//        设置压缩等级
        zipArchiveOutputStream.setLevel(Deflater.BEST_SPEED);
//        设置压缩的字符编码
        zipArchiveOutputStream.setEncoding("UTF-8");

        for (int i = 0; i < paths.size(); i++) {
            File file = new File(basePath + paths.get(i));
            inputStreamSupplier = () -> {
                try {
                    return new FileInputStream(file);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    return new NullInputStream(0);
                }
            };
            String tempStr = paths.get(i);
            tempStr = tempStr.replace("\\", "/");
            final String[] split = tempStr.split("/");
            String temp = split[split.length - 1];
            int index = temp.lastIndexOf('.');
            String fileName = temp.substring(0, index) + "(唯一识别码:" + i + ")." + temp.substring(index + 1);
            ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(fileName);
            zipArchiveEntry.setMethod(ZipArchiveEntry.DEFLATED);
            zipArchiveEntry.setSize(file.length());
            parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
        }

        //        将数据写入zip输出流
        parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
//        相关流的关闭
        zipArchiveOutputStream.close();
        outputStream.close();
//       返回压缩后文件了路径
        return tempFile.getAbsolutePath();
    }


    public static void unZippress(String filePath, String destDir) {
        byte[] buffer = new byte[1024];

        try (ZipInputStream zis = new ZipInputStream(new FileInputStream( filePath))) {
            ZipEntry entry = zis.getNextEntry();
            while (entry != null) {
                File file = new File(destDir, entry.getName());
                if (entry.isDirectory()) {
                    file.mkdirs();
                } else {
                    File parent = file.getParentFile();
                    if (!parent.exists()) {
                        parent.mkdirs();
                    }
                    try (FileOutputStream fos = new FileOutputStream(file)) {
                        int len;
                        while ((len = zis.read(buffer)) > 0) {
                            fos.write(buffer, 0, len);
                        }
                    }
                }
                entry = zis.getNextEntry();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }




}
